package lang

import (
	"fmt"
	"jvmgo/instructions/base"
	"jvmgo/native"
	"jvmgo/rtda"
	"jvmgo/rtda/heap"
	"runtime"
	"time"
)

var sysProps = map[string]string{
	"java.version":         "1.8.0",
	"java.vendor":          "liyuan",
	"java.vendor.url":      "empty_liyuan",
	"java.home":            "xxx",
	"java.class.version":   "52.0",
	"java.class.path":      "todo",
	"java.awt.graphicsenv": "sun.awt.CGraphicsEnv",
	"os.name":              runtime.GOOS,
	"os.arch":              runtime.GOARCH,
	"os.version":           runtime.Version(),
	"file.separator":       "/",
	"path.separator":       ":",
	"line.separator":       "\n",
	"user.name":            "",
	"user.home":            "",
	"user.dir":             ".",
	"user.country":         "CN",
	"file.encoding":        "utf-8",
	"sun.stdout.encoding":  "utf-8",
	"sun.stderr.encoding":  "utf-8",
}

func init() {
	native.Register("java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V", arraycopy)
	native.Register("java/lang/System", "initProperties", "(Ljava/util/Properties;)Ljava/util/Properties;", initProperties)
	native.Register("java/lang/System", "setIn0", "(Ljava/io/InputStream;)V", setIn0)
	native.Register("java/lang/System", "setOut0", "(Ljava/io/PrintStream;)V", setOut0)
	native.Register("java/lang/System", "setErr0", "(Ljava/io/PrintStream;)V", setErr0)
	native.Register("java/lang/System", "currentTimeMillis", "()J", currentTimeMillis)
	//native.Register("java/lang/System", "mapLibraryName", "(Ljava/lang/String;)Ljava/lang/String;", mapLibraryName)
}

// Author: yli124
// HINT: this code can not be found in jvmgo
// zip => libzip.dylib
// instrument =>
var libMap = map[string]string{
	"zip": "libzip.dylib",
}

// TODO , ？bug？
func mapLibraryName(frame *rtda.Frame) {
	vars := frame.LocalVars()
	var0 := vars.GetRef(0)
	goName := heap.GoString(var0)
	classLoader := frame.Method().Class().Loader()
	if name, ok := libMap[goName]; ok {
		retName := heap.JString(classLoader, name)
		frame.OperandStack().PushRef(retName)
	} else {
		fmt.Println("namexxx:", goName, name)
	}
}

// public static native void arraycopy(Object src,  int  srcPos,
//
//	Object dest, int destPos,
//	int length);
func arraycopy(frame *rtda.Frame) {
	vars := frame.LocalVars()
	src := vars.GetRef(0)
	srcPos := vars.GetInt(1)
	dest := vars.GetRef(2)
	destPos := vars.GetInt(3)
	length := vars.GetInt(4)
	if src == nil || dest == nil {
		panic("java.lang.NullPointerException")
	}
	if !checkArrayCopy(src, dest) {
		panic("java.lang.ArrayStoreException")
	}
	if srcPos < 0 || destPos < 0 || length < 0 || srcPos+length > src.ArrayLength() || destPos+length > dest.ArrayLength() {
		panic("java.lang.IndexOutOfBoundsException")
	}
	// [a, b, c, 1, '', '', '', '', '', '', '', '', '', '', '', '']
	// char[] dest = new char[to - from ];
	// 0
	// length: Math.min(original.length - from, newLength)
	heap.ArrayCopy(src, dest, srcPos, destPos, length)
}

func checkArrayCopy(src *heap.Object, dest *heap.Object) bool {
	srcClass := src.Class()
	destClass := dest.Class()
	if !srcClass.IsArray() || !destClass.IsArray() {
		return false
	}
	if srcClass.ComponentClass().IsPrimitive() || destClass.ComponentClass().IsPrimitive() {
		return srcClass == destClass
	}
	return true
}

func initProperties(frame *rtda.Frame) {
	vars := frame.LocalVars()
	props := vars.GetRef(0)
	stack := frame.OperandStack()
	stack.PushRef(props)

	setPropMethod := props.Class().GetInstanceMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")
	thread := frame.Thread()
	for k, v := range sysProps {
		classLoader := frame.Method().Loader()
		jKey := heap.JString(classLoader, k)
		jVal := heap.JString(classLoader, v)
		ops := rtda.NewOperandStack(3)
		ops.PushRef(props)
		ops.PushRef(jKey)
		ops.PushRef(jVal)
		shimFrame := rtda.NewShimFrame(thread, ops)
		thread.PushFrame(shimFrame)
		base.InvokeMethod(shimFrame, setPropMethod)
	}
}

func setIn0(frame *rtda.Frame) {
	in := frame.LocalVars().GetRef(0)
	sysClass := frame.Method().Class()
	sysClass.SetRefVar("in", "Ljava/io/InputStream;", in)
}

func setOut0(frame *rtda.Frame) {
	out := frame.LocalVars().GetRef(0)
	sysClass := frame.Method().Class()
	sysClass.SetRefVar("out", "Ljava/io/PrintStream;", out)
}

func setErr0(frame *rtda.Frame) {
	err := frame.LocalVars().GetRef(0)
	sysClass := frame.Method().Class()
	sysClass.SetRefVar("err", "Ljava/io/PrintStream;", err)
}

func currentTimeMillis(frame *rtda.Frame) {
	millis := time.Now().UnixNano() / int64(time.Millisecond)
	frame.OperandStack().PushLong(millis)
}
